package com.happy3w.bean.factory;

import com.happy3w.java.ext.Pair;
import com.happy3w.toolkits.message.MessageRecorderException;
import lombok.Getter;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Parameter;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;

/**
 * 通过Config构建对应Bean的助手，代码一般写成类似格式
 * abstract class C { // 配置类
 *
 * }
 *
 * abstract class S { // Bean类
 *
 * }
 *
 * 具体实现方法
 * class A extend S {
 *     class Config extends C {
 *
 *     }
 * }
 * 使用的时候按照下面方法初始化
 * ConfigBeanHelper.initConfigType(C.class.getPackage().getName(), C.class); // 发现所有C的子类
 * ConfigBeanHelper.initBeanTypes(S.class.getPackage().getName(), S.class); // 发现所有S的子类
 */
@Getter
public class ConfigBeanHelper {
    public static ConfigBeanHelper INSTANCE;

    /**
     * 从Json中解码各种类型的工具
     */
    protected final DecodeHelper decodeHelper;

    /**
     * 在给定的package中发现类型的工具
     */
    protected final TypeDetector typeDetector;

    /**
     * 实例加载工具.用于构造bean时提供特定类型参数实例
     * String为参数名称
     * Class为参数类型
     * 返回找到的bean
     */
    protected final BiFunction<String, Class, Object> beanLoader;

    /**
     * 构造函数缓存，用于加速Bean的构建过程
     * config type -> constructor
     */
    protected final Map<Class, BeanConstructorConfig> beanConstructorConfigs = new HashMap<>();

    public ConfigBeanHelper(DecodeHelper decodeHelper, TypeDetector typeDetector, BiFunction<String, Class, Object> beanLoader) {
        this.decodeHelper = decodeHelper;
        this.typeDetector = typeDetector;
        this.beanLoader = beanLoader;
        if (INSTANCE == null) {
            INSTANCE = this;
        }
    }

    public void initConfigType(String packageName, Class... configTypes) {
        Class[] types = typeDetector.detect(packageName, configTypes)
                .map(Pair::getValue)
                .toArray(size -> new Class[size]);
        if (types.length == 0) {
            throw new MessageRecorderException(MessageFormat.format(
                    "No type find in {0} for {1}",
                    packageName, Arrays.asList(configTypes)));
        }
        decodeHelper.registerSubTypes(types);
    }

    public void initBeanTypes(Class<?> configType, Class<?> beanType, String... packageNames) {
        BeanConstructorConfig.initBeanTypes(configType, beanType, beanConstructorConfigs, typeDetector, packageNames);
    }

    public <T> T createBean(Object configItem) {
        BeanConstructorConfig constructorConfig = beanConstructorConfigs.get(configItem.getClass());
        if (constructorConfig == null) {
            throw new MessageRecorderException(MessageFormat.format(
                    "Can''t find bean constructor: type:{1}.",
                    configItem.getClass()));
        }

        return (T) newInstance(configItem, constructorConfig.getBeanConstructor());
    }

    public <T, S> S newInstance(T config, Constructor<S> constructor) {
        Class<?> configType = config.getClass();
        Parameter[] parameters = constructor.getParameters();
        Object[] params = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Class<?> pType = parameter.getType();
            if (configType.isAssignableFrom(pType)) {
                params[i] = config;
            } else {
                params[i] = beanLoader.apply(parameter.getName(), pType);
            }
        }
        try {
            return constructor.newInstance(params);
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
            throw new MessageRecorderException("Failed to build bean", e);
        }
    }
}
